/*
* (c) Copyright 2010-2011 AgileBirds
*
* This file is part of OpenFlexo.
*
* OpenFlexo is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenFlexo is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenFlexo. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.openflexo.javaparser;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.Reader;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import com.thoughtworks.qdox.JavaDocBuilder;
import com.thoughtworks.qdox.Searcher;
import com.thoughtworks.qdox.directorywalker.DirectoryScanner;
import com.thoughtworks.qdox.directorywalker.FileVisitor;
import com.thoughtworks.qdox.directorywalker.SuffixFilter;
import com.thoughtworks.qdox.model.ClassLibrary;
import com.thoughtworks.qdox.model.DefaultDocletTagFactory;
import com.thoughtworks.qdox.model.DocletTagFactory;
import com.thoughtworks.qdox.model.JavaClass;
import com.thoughtworks.qdox.model.JavaClassCache;
import com.thoughtworks.qdox.model.JavaSource;
import com.thoughtworks.qdox.parser.Lexer;
import com.thoughtworks.qdox.parser.ParseException;
import com.thoughtworks.qdox.parser.impl.JFlexLexer;
import com.thoughtworks.qdox.parser.impl.Parser;
import com.thoughtworks.qdox.parser.structs.ClassDef;
import com.thoughtworks.qdox.parser.structs.FieldDef;
import com.thoughtworks.qdox.parser.structs.MethodDef;
/**
* Code duplicated from JavaDocBuilder with following modifications: 1. Allow to use a custom class library 2. Use FJPModelBuilder instead
* of ModelBuilder
*
* @author sylvain
*
*/
public class FJPJavaDocBuilder extends JavaDocBuilder implements Serializable, JavaClassCache {
private Map classes = new HashMap();
private ClassLibrary classLibrary;
private List<JavaSource> sources = new ArrayList<JavaSource>();
private DocletTagFactory docletTagFactory;
private String encoding = System.getProperty("file.encoding");
private boolean debugLexer;
private boolean debugParser;
public FJPJavaDocBuilder(ClassLibrary classLibrary) {
this(new DefaultDocletTagFactory(), classLibrary);
}
public FJPJavaDocBuilder(DocletTagFactory docletTagFactory, ClassLibrary classLibrary) {
super(docletTagFactory);
this.docletTagFactory = docletTagFactory;
this.classLibrary = classLibrary;
}
private void addClasses(JavaSource source) {
Set resultSet = new HashSet();
addClassesRecursive(source, resultSet);
JavaClass[] javaClasses = (JavaClass[]) resultSet.toArray(new JavaClass[resultSet.size()]);
for (int classIndex = 0; classIndex < javaClasses.length; classIndex++) {
JavaClass cls = javaClasses[classIndex];
addClass(cls);
}
}
private void addClass(JavaClass cls) {
classes.put(cls.getFullyQualifiedName(), cls);
cls.setJavaClassCache(this);
}
@Override
public JavaClass getClassByName(String name) {
if (name == null) {
return null;
}
JavaClass result = (JavaClass) classes.get(name);
if (result == null) {
result = classLibrary.getClassByName(name);
}
if (result == null) {
// Try to make a binary class out of it
result = createBinaryClass(name);
if (result != null) {
addClass(result);
} else {
result = createUnknownClass(name);
}
}
return result;
}
private JavaClass createUnknownClass(String name) {
FJPModelBuilder unknownBuilder = new FJPModelBuilder(classLibrary, docletTagFactory);
ClassDef classDef = new ClassDef();
classDef.name = name;
unknownBuilder.beginClass(classDef);
unknownBuilder.endClass();
JavaSource unknownSource = unknownBuilder.getSource();
JavaClass result = unknownSource.getClasses()[0];
return result;
}
private JavaClass createBinaryClass(String name) {
// First see if the class exists at all.
Class clazz = classLibrary.getClass(name);
if (clazz == null) {
return null;
} else {
// Create a new builder and mimic the behaviour of the parser.
// We're getting all the information we need via reflection instead.
FJPModelBuilder binaryBuilder = new FJPModelBuilder(classLibrary, docletTagFactory);
// Set the package name and class name
String packageName = getPackageName(name);
binaryBuilder.addPackage(packageName);
ClassDef classDef = new ClassDef();
classDef.name = getClassName(name);
// Set the extended class and interfaces.
Class[] interfaces = clazz.getInterfaces();
if (clazz.isInterface()) {
// It's an interface
classDef.type = ClassDef.INTERFACE;
for (int i = 0; i < interfaces.length; i++) {
Class anInterface = interfaces[i];
classDef.extendz.add(anInterface.getName());
}
} else {
// It's a class
for (int i = 0; i < interfaces.length; i++) {
Class anInterface = interfaces[i];
classDef.implementz.add(anInterface.getName());
}
Class superclass = clazz.getSuperclass();
if (superclass != null) {
classDef.extendz.add(superclass.getName());
}
}
addModifiers(classDef.modifiers, clazz.getModifiers());
binaryBuilder.beginClass(classDef);
// add the constructors
Constructor[] constructors = clazz.getConstructors();
for (int i = 0; i < constructors.length; i++) {
addMethodOrConstructor(constructors[i], binaryBuilder);
}
// add the methods
Method[] methods = clazz.getMethods();
for (int i = 0; i < methods.length; i++) {
// Ignore methods defined in superclasses
if (methods[i].getDeclaringClass() == clazz) {
addMethodOrConstructor(methods[i], binaryBuilder);
}
}
Field[] fields = clazz.getFields();
for (int i = 0; i < fields.length; i++) {
if (fields[i].getDeclaringClass() == clazz) {
addField(fields[i], binaryBuilder);
}
}
binaryBuilder.endClass();
JavaSource binarySource = binaryBuilder.getSource();
// There is always only one class in a "binary" source.
JavaClass result = binarySource.getClasses()[0];
return result;
}
}
private void addModifiers(Set set, int modifier) {
String modifierString = Modifier.toString(modifier);
for (StringTokenizer stringTokenizer = new StringTokenizer(modifierString); stringTokenizer.hasMoreTokens();) {
set.add(stringTokenizer.nextToken());
}
}
private void addField(Field field, FJPModelBuilder binaryBuilder) {
FieldDef fieldDef = new FieldDef();
Class fieldType = field.getType();
fieldDef.name = field.getName();
fieldDef.type = getTypeName(fieldType);
fieldDef.dimensions = getDimension(fieldType);
binaryBuilder.addField(fieldDef);
}
@SuppressWarnings("unchecked")
// because call library in java 1.4 code style
private void addMethodOrConstructor(Member member, FJPModelBuilder binaryBuilder) {
MethodDef methodDef = new MethodDef();
// The name of constructors are qualified. Need to strip it.
// This will work for regular methods too, since -1 + 1 = 0
int lastDot = member.getName().lastIndexOf('.');
methodDef.name = member.getName().substring(lastDot + 1);
addModifiers(methodDef.modifiers, member.getModifiers());
Class[] exceptions;
Class[] parameterTypes;
if (member instanceof Method) {
methodDef.constructor = false;
// For some stupid reason, these methods are not defined in Member,
// but in both Method and Construcotr.
exceptions = ((Method) member).getExceptionTypes();
parameterTypes = ((Method) member).getParameterTypes();
Class returnType = ((Method) member).getReturnType();
methodDef.returns = getTypeName(returnType);
methodDef.dimensions = getDimension(returnType);
} else {
methodDef.constructor = true;
exceptions = ((Constructor) member).getExceptionTypes();
parameterTypes = ((Constructor) member).getParameterTypes();
}
for (int j = 0; j < exceptions.length; j++) {
Class exception = exceptions[j];
methodDef.exceptions.add(exception.getName());
}
for (int j = 0; j < parameterTypes.length; j++) {
FieldDef param = new FieldDef();
Class parameterType = parameterTypes[j];
param.name = "p" + j;
param.type = getTypeName(parameterType);
param.dimensions = getDimension(parameterType);
methodDef.params.add(param);
}
binaryBuilder.addMethod(methodDef);
}
private static final int getDimension(Class c) {
return c.getName().lastIndexOf('[') + 1;
}
private static String getTypeName(Class c) {
return c.getComponentType() != null ? c.getComponentType().getName() : c.getName();
}
private String getPackageName(String fullClassName) {
int lastDot = fullClassName.lastIndexOf('.');
return lastDot == -1 ? "" : fullClassName.substring(0, lastDot);
}
private String getClassName(String fullClassName) {
int lastDot = fullClassName.lastIndexOf('.');
return lastDot == -1 ? fullClassName : fullClassName.substring(lastDot + 1);
}
@Override
public JavaSource addSource(Reader reader, String sourceInfo) {
FJPModelBuilder builder = new FJPModelBuilder(classLibrary, docletTagFactory);
Lexer lexer = new JFlexLexer(reader);
Parser parser = new Parser(lexer, builder);
parser.setDebugLexer(debugLexer);
parser.setDebugParser(debugParser);
try {
parser.parse();
} catch (ParseException e) {
e.setSourceInfo(sourceInfo);
throw e;
}
JavaSource source = builder.getSource();
sources.add(source);
addClasses(source);
return source;
}
@Override
public JavaSource addSource(URL url) throws IOException, FileNotFoundException {
JavaSource source = addSource(new InputStreamReader(url.openStream(), encoding), url.toExternalForm());
source.setURL(url);
return source;
}
@Override
public JavaSource[] getSources() {
return sources.toArray(new JavaSource[sources.size()]);
}
private void addClassesRecursive(JavaSource javaSource, Set resultSet) {
JavaClass[] classes = javaSource.getClasses();
for (int j = 0; j < classes.length; j++) {
JavaClass javaClass = classes[j];
addClassesRecursive(javaClass, resultSet);
}
}
private void addClassesRecursive(JavaClass javaClass, Set set) {
// Add the class...
set.add(javaClass);
// And recursively all of its inner classes
JavaClass[] innerClasses = javaClass.getNestedClasses();
for (int i = 0; i < innerClasses.length; i++) {
JavaClass innerClass = innerClasses[i];
addClassesRecursive(innerClass, set);
}
}
@Override
public void addSourceTree(File file) {
DirectoryScanner scanner = new DirectoryScanner(file);
scanner.addFilter(new SuffixFilter(".java"));
scanner.scan(new FileVisitor() {
@Override
public void visitFile(File currentFile) {
try {
addSource(currentFile);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Unsupported encoding : " + encoding);
} catch (IOException e) {
throw new RuntimeException("Cannot read file : " + currentFile.getName());
}
}
});
}
@Override
public List<JavaClass> search(Searcher searcher) {
List<JavaClass> results = new LinkedList<JavaClass>();
for (Iterator iterator = classLibrary.all().iterator(); iterator.hasNext();) {
String clsName = (String) iterator.next();
JavaClass cls = getClassByName(clsName);
if (searcher.eval(cls)) {
results.add(cls);
}
}
return results;
}
@Override
public ClassLibrary getClassLibrary() {
return classLibrary;
}
/**
* Note that after loading JavaDocBuilder classloaders need to be re-added.
*/
public static FJPJavaDocBuilder load(File file) throws IOException {
FileInputStream fis = new FileInputStream(file);
ObjectInputStream in = new ObjectInputStream(fis);
FJPJavaDocBuilder builder = null;
try {
builder = (FJPJavaDocBuilder) in.readObject();
} catch (ClassNotFoundException e) {
throw new Error("Couldn't load class : " + e.getMessage());
} finally {
in.close();
fis.close();
}
return builder;
}
@Override
public void setEncoding(String encoding) {
this.encoding = encoding;
}
/**
* Forces QDox to dump tokens returned from lexer to System.err.
*/
@Override
public void setDebugLexer(boolean debugLexer) {
this.debugLexer = debugLexer;
}
/**
* Forces QDox to dump parser states to System.out.
*/
@Override
public void setDebugParser(boolean debugParser) {
this.debugParser = debugParser;
}
}